#pragma once

#include "node.h"
#include "../opengl/frame_buffer.h"

OPEN_JLIB_NS


struct CameraNode : public Node {

  using Node::Node;

  void removeSelf (bool cleanup) override;

  virtual void onDrawBegan () = 0;
  virtual void onDrawEnded () = 0;

  virtual Mat4 projectionMatrix () = 0;
  virtual Mat4 viewMatrix () = 0;

private:
  List _cameraList;
  friend struct SpaceNode;
};



struct WindowCamera : public CameraNode {

  using CameraNode::CameraNode;

  void onDrawBegan () override;
  void onDrawEnded () override;

  Mat4 projectionMatrix () override;
  Mat4 viewMatrix () override;
};




struct FirstPersonCamera : public CameraNode {

  using CameraNode::CameraNode;

  bool init (float fovy, float near = 1.0F, float far = 100000.0F) override;

  void initDraw () override;
  void endDraw () override;
  mat4 getProjectionMatrix () override;
  mat4 getViewMatrix () override;
  const mat4 &getLocalMatrix () override;

  void move (vec3 vector) noexcept;
  void moveUp (float distance) noexcept;
  void moveDown (float distance) noexcept;
  void moveLeft (float distance) noexcept;
  void moveRight (float distance) noexcept;
  void moveForward (float distance) noexcept;
  void moveBackward (float distance) noexcept;

  inline void setPitch (float pitch) noexcept { this->setRotationX(d2r(pitch)); }
  inline void setYaw (float yaw) noexcept { this->setRotationZ(d2r(yaw)); }
  inline void setRoll (float roll) noexcept { this->setRotationY(d2r(roll)); }
  inline void addPitch (float pitch) noexcept { this->setRotationX(this->getRotationX() + d2r(pitch)); }
  inline void addYaw (float yaw) noexcept { this->setRotationZ(this->getRotationZ() + d2r(yaw)); }
  inline void addRoll (float roll) noexcept { this->setRotationY(this->getRotationY() + d2r(roll)); }

  inline float getPitch () noexcept { return r2d(this->getRotationX()); }
  inline float getYaw () noexcept { return r2d(this->getRotationZ()); }
  inline float getRoll () noexcept { return r2d(this->getRotationY()); }

protected:
  mat4 _projection;
  mat4 _lookat;
  float _fovy;
  float _aspect {0.0F};
  float _near;
  float _far;
  flags_type _firstPersonCameraFlags {ProjectionMatrixDirty | LookatMatrixDirty};

  constexpr static flags_type ProjectionMatrixDirty = 1;
  constexpr static flags_type LookatMatrixDirty = 2;
}; // struct FirstPersonCamera




struct ThirdPersonCamera : public CameraNode {

  using CameraNode::CameraNode;

  bool init (float fovy, float near = 1.0F, float far = 100000.0F) noexcept;

  void initDraw () override;
  void endDraw () override;
  mat4 getProjectionMatrix () override;
  mat4 getViewMatrix () override;
  const mat4 &getLocalMatrix () override;

  void setTarget (vec3 target) noexcept;
  void setRadius (float radius) noexcept;

  void move (vec3 vector) noexcept;
  void moveUp (float distance) noexcept;
  void moveDown (float distance) noexcept;
  void moveLeft (float distance) noexcept;
  void moveRight (float distance) noexcept;
  void moveForward (float distance) noexcept;
  void moveBackward (float distance) noexcept;

  void setPitch (float pitch) noexcept;
  void setYaw (float yaw) noexcept;
  void setRoll (float roll) noexcept;
  void addPitch (float pitch) noexcept;
  void addYaw (float yaw) noexcept;
  void addRoll (float roll) noexcept;

  inline float getPitch () noexcept { return r2d(this->getRotationX()); }
  inline float getYaw () noexcept { return r2d(this->getRotationZ()); }
  inline float getRoll () noexcept { return r2d(this->getRotationY()); }

protected:
  mat4 _projection;
  mat4 _lookat;
  vec3 _target {0.0F, 0.0F, 0.0F};
  float _fovy;
  float _aspect {0.0F};
  float _near;
  float _far;
  flags_type _firstPersonCameraFlags {ProjectionMatrixDirty | LookatMatrixDirty};

  constexpr static flags_type ProjectionMatrixDirty = 1;
  constexpr static flags_type LookatMatrixDirty = 2;

}; // struct FirstPersonCamera



struct Texture;
struct CanvasCamera : public CameraNode {

  using CameraNode::CameraNode;

  bool init (int w, int h) noexcept;
  bool init (Texture *tex) noexcept;

  void initDraw () override;
  void endDraw () override;

  inline int width () const noexcept { return this->_canvas->width(); }
  inline int height () const noexcept { return this->_canvas->height(); }
  inline size sizei () const noexcept { return this->_canvas->sizei(); }
  inline vec2 sizef () const noexcept { return this->_canvas->sizef(); }
  inline Texture *colorTex () noexcept { return this->_canvas->colorTex(); }
  inline Texture *depthTex () noexcept { return this->_canvas->depthTex(); }

protected:
  Ptr<FrameBuffer> _canvas;
};


CLOSE_JLIB_NS
